/**
 * Property-based tests for validation utilities
 */

import { describe, it, expect } from 'vitest';
import fc from 'fast-check';
import { 
  isValidEmotionReport, 
  isValidEmotionItem, 
  sanitizeInput, 
  containsDangerousContent,
  isValidAnalyzeEmotionResponse,
  isValidGenerateImageResponse
} from './validation';
import type { EmotionItem } from '../types';

// Arbitrary for generating valid EmotionItem
const emotionItemArbitrary = fc.record({
  name: fc.string({ minLength: 1 }),
  intensity: fc.integer({ min: 0, max: 100 }),
  color: fc.string({ minLength: 1 }),
  icon: fc.string()
});

// Arbitrary for generating valid EmotionReport
const emotionReportArbitrary = fc.record({
  emotions: fc.array(emotionItemArbitrary, { minLength: 1 }),
  suggestions: fc.array(fc.string(), { minLength: 1 }),
  summary: fc.string(),
  timestamp: fc.date()
});

describe('EmotionItem Validation', () => {
  it('should validate correctly structured EmotionItems', () => {
    fc.assert(
      fc.property(emotionItemArbitrary, (item) => {
        expect(isValidEmotionItem(item)).toBe(true);
      }),
      { numRuns: 100 }
    );
  });

  it('should reject items with intensity outside 0-100 range', () => {
    fc.assert(
      fc.property(
        fc.record({
          name: fc.string({ minLength: 1 }),
          intensity: fc.oneof(
            fc.integer({ max: -1 }),
            fc.integer({ min: 101 })
          ),
          color: fc.string({ minLength: 1 }),
          icon: fc.string()
        }),
        (item) => {
          expect(isValidEmotionItem(item)).toBe(false);
        }
      ),
      { numRuns: 100 }
    );
  });
});


describe('EmotionReport Validation', () => {
  /**
   * **Feature: emotion-3d-website, Property 6: Emotion Report Structure Completeness**
   * **Validates: Requirements 3.3, 3.4**
   * 
   * For any generated emotion report, the report SHALL contain:
   * (1) a non-empty emotions array with each emotion having name, intensity (0-100), and color
   * (2) a non-empty suggestions array
   * (3) a summary string
   */
  it('should validate reports with complete structure - Property 6', () => {
    fc.assert(
      fc.property(emotionReportArbitrary, (report) => {
        // Valid reports should pass validation
        expect(isValidEmotionReport(report)).toBe(true);
        
        // Verify structure completeness
        expect(report.emotions.length).toBeGreaterThan(0);
        expect(report.emotions.every((e: EmotionItem) => 
          typeof e.name === 'string' &&
          e.intensity >= 0 && e.intensity <= 100 &&
          typeof e.color === 'string'
        )).toBe(true);
        expect(report.suggestions.length).toBeGreaterThan(0);
        expect(typeof report.summary).toBe('string');
      }),
      { numRuns: 100 }
    );
  });

  it('should reject reports with empty emotions array', () => {
    fc.assert(
      fc.property(
        fc.record({
          emotions: fc.constant([]),
          suggestions: fc.array(fc.string(), { minLength: 1 }),
          summary: fc.string(),
          timestamp: fc.date()
        }),
        (report) => {
          expect(isValidEmotionReport(report)).toBe(false);
        }
      ),
      { numRuns: 100 }
    );
  });

  it('should reject reports with empty suggestions array', () => {
    fc.assert(
      fc.property(
        fc.record({
          emotions: fc.array(emotionItemArbitrary, { minLength: 1 }),
          suggestions: fc.constant([]),
          summary: fc.string(),
          timestamp: fc.date()
        }),
        (report) => {
          expect(isValidEmotionReport(report)).toBe(false);
        }
      ),
      { numRuns: 100 }
    );
  });

  it('should reject null or undefined reports', () => {
    expect(isValidEmotionReport(null)).toBe(false);
    expect(isValidEmotionReport(undefined)).toBe(false);
  });

  it('should reject reports missing required fields', () => {
    fc.assert(
      fc.property(
        fc.oneof(
          fc.record({ emotions: fc.array(emotionItemArbitrary, { minLength: 1 }) }),
          fc.record({ suggestions: fc.array(fc.string(), { minLength: 1 }) }),
          fc.record({ summary: fc.string() })
        ),
        (partialReport) => {
          expect(isValidEmotionReport(partialReport)).toBe(false);
        }
      ),
      { numRuns: 100 }
    );
  });
});


describe('Input Sanitization', () => {
  /**
   * **Feature: emotion-3d-website, Property 10: Input Sanitization**
   * **Validates: Requirements 6.2**
   * 
   * For any user input containing potentially dangerous characters (script tags, 
   * SQL injection patterns), the sanitization function SHALL escape or remove 
   * these characters before sending to APIs.
   */
  it('should sanitize script tags - Property 10', () => {
    fc.assert(
      fc.property(
        fc.tuple(
          fc.string(), // prefix
          fc.string(), // script content
          fc.string()  // suffix
        ),
        ([prefix, content, suffix]) => {
          const maliciousInput = `${prefix}<script>${content}</script>${suffix}`;
          const sanitized = sanitizeInput(maliciousInput);
          
          // Should not contain script tags
          expect(sanitized).not.toContain('<script>');
          expect(sanitized).not.toContain('</script>');
        }
      ),
      { numRuns: 100 }
    );
  });

  it('should escape HTML entities - Property 10', () => {
    fc.assert(
      fc.property(
        fc.string(),
        (input) => {
          const sanitized = sanitizeInput(input);
          
          // Should not contain unescaped < or > (except in escaped form)
          expect(sanitized).not.toMatch(/(?<!&lt|&gt|&amp|&quot|&#x27)[<>]/);
        }
      ),
      { numRuns: 100 }
    );
  });

  it('should handle SQL injection patterns - Property 10', () => {
    const sqlInjectionExamples = [
      "'; DROP TABLE users; --",
      "1 OR 1=1",
      "admin'--",
      "SELECT * FROM users",
      "1; DELETE FROM orders"
    ];

    for (const injection of sqlInjectionExamples) {
      expect(containsDangerousContent(injection)).toBe(true);
      const sanitized = sanitizeInput(injection);
      // Sanitized output should not contain the dangerous SQL keywords
      expect(sanitized.toUpperCase()).not.toMatch(/\b(DROP|SELECT|DELETE)\b/);
    }
  });

  it('should preserve safe content - Property 10', () => {
    fc.assert(
      fc.property(
        fc.stringMatching(/^[a-zA-Z0-9\s,.!?]+$/), // Safe alphanumeric content
        (safeInput) => {
          const sanitized = sanitizeInput(safeInput);
          // Safe content should be mostly preserved (trimmed)
          expect(sanitized).toBe(safeInput.trim());
        }
      ),
      { numRuns: 100 }
    );
  });

  it('should return empty string for non-string input - Property 10', () => {
    expect(sanitizeInput(null as unknown as string)).toBe('');
    expect(sanitizeInput(undefined as unknown as string)).toBe('');
    expect(sanitizeInput(123 as unknown as string)).toBe('');
    expect(sanitizeInput({} as unknown as string)).toBe('');
  });
});


describe('API Response Validation', () => {
  /**
   * **Feature: emotion-3d-website, Property 11: Response Validation**
   * **Validates: Requirements 6.3**
   * 
   * For any API response, the validation function SHALL return false for responses 
   * missing required fields and true only for properly structured responses.
   */

  describe('AnalyzeEmotionResponse Validation - Property 11', () => {
    // Arbitrary for valid successful response
    const validSuccessResponseArbitrary = fc.record({
      success: fc.constant(true),
      report: emotionReportArbitrary,
      imagePrompt: fc.string({ minLength: 1 })
    });

    // Arbitrary for valid error response
    const validErrorResponseArbitrary = fc.record({
      success: fc.constant(false),
      error: fc.string({ minLength: 1 })
    });

    it('should validate properly structured success responses', () => {
      fc.assert(
        fc.property(validSuccessResponseArbitrary, (response) => {
          expect(isValidAnalyzeEmotionResponse(response)).toBe(true);
        }),
        { numRuns: 100 }
      );
    });

    it('should validate properly structured error responses', () => {
      fc.assert(
        fc.property(validErrorResponseArbitrary, (response) => {
          expect(isValidAnalyzeEmotionResponse(response)).toBe(true);
        }),
        { numRuns: 100 }
      );
    });

    it('should reject responses missing success field', () => {
      fc.assert(
        fc.property(
          fc.record({
            report: emotionReportArbitrary,
            imagePrompt: fc.string()
          }),
          (response) => {
            expect(isValidAnalyzeEmotionResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject success responses missing report', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(true),
            imagePrompt: fc.string()
          }),
          (response) => {
            expect(isValidAnalyzeEmotionResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject success responses missing imagePrompt', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(true),
            report: emotionReportArbitrary
          }),
          (response) => {
            expect(isValidAnalyzeEmotionResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject error responses missing error message', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(false)
          }),
          (response) => {
            expect(isValidAnalyzeEmotionResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject null or undefined responses', () => {
      expect(isValidAnalyzeEmotionResponse(null)).toBe(false);
      expect(isValidAnalyzeEmotionResponse(undefined)).toBe(false);
    });
  });

  describe('GenerateImageResponse Validation - Property 11', () => {
    // Arbitrary for valid successful response
    const validImageSuccessArbitrary = fc.record({
      success: fc.constant(true),
      imageUrl: fc.webUrl()
    });

    // Arbitrary for valid error response
    const validImageErrorArbitrary = fc.record({
      success: fc.constant(false),
      error: fc.string({ minLength: 1 })
    });

    it('should validate properly structured success responses', () => {
      fc.assert(
        fc.property(validImageSuccessArbitrary, (response) => {
          expect(isValidGenerateImageResponse(response)).toBe(true);
        }),
        { numRuns: 100 }
      );
    });

    it('should validate properly structured error responses', () => {
      fc.assert(
        fc.property(validImageErrorArbitrary, (response) => {
          expect(isValidGenerateImageResponse(response)).toBe(true);
        }),
        { numRuns: 100 }
      );
    });

    it('should reject success responses with empty imageUrl', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(true),
            imageUrl: fc.constant('')
          }),
          (response) => {
            expect(isValidGenerateImageResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject success responses missing imageUrl', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(true)
          }),
          (response) => {
            expect(isValidGenerateImageResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject error responses missing error message', () => {
      fc.assert(
        fc.property(
          fc.record({
            success: fc.constant(false)
          }),
          (response) => {
            expect(isValidGenerateImageResponse(response)).toBe(false);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('should reject null or undefined responses', () => {
      expect(isValidGenerateImageResponse(null)).toBe(false);
      expect(isValidGenerateImageResponse(undefined)).toBe(false);
    });
  });
});
